/*LICENSE_BEGIN
 * ******************************************************************
 * All original parts of SoccerScope2003 have copyright (2003) by
 * Team YowAI, represented by Mr. Shuhei Shiota
 * The Department of Creative Informatics
 * The Graduate School of Information Science and Technology
 * The University of Tokyo
 * 
 * Additional classes (except libraries in /lib) were developed by 
 * the Dainamite Robocup-Team of the DAI-Labor, TU-Berlin 
 * (www.dai-labor.de)
 * 
 * Extended/new classes/packages are:
 *   soccerscope.analyzer.LogfileAnalyzer
 *   soccerscope.daistatistic.*
 *   soccerscope.daistatistic.charts.*
 *   soccerscope.daistatistic.tools.*
 *   soccerscope.db.*
 *   soccerscope.dialog.*
 *   soccerscope.model.PlayerWorldModel
 *   soccerscope.model.SimplePlayer
 *   soccerscope.net.AgentConnector
 *   soccerscope.training.*
 *   soccerscope.training.conditions.*
 * ******************************************************************
LICENSE_END*/
package soccerscope.training;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.LinkedList;

import robocup.component.actions.Action;
import robocup.component.actions.CoachAction;
import robocup.component.actions.SayAction;
import robocup.component.geometry.Vektor;
import robocup.component.speechacts.MessageFactory;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import soccerscope.dialog.TrainingStatsDialog;
import soccerscope.model.ClientScene;
import soccerscope.model.PlayMode;
import soccerscope.model.WorldModel;
import soccerscope.model.PlayMode.PLAY_MODE;
import soccerscope.training.conditions.BallPossessionCondition;
import soccerscope.training.conditions.CycleCondition;
import soccerscope.training.conditions.GameCondition;
import soccerscope.training.conditions.PlayModeCondition;
import soccerscope.util.Time;

import com.thoughtworks.xstream.XStream;

public class TrainingModel {
	public static int run = 1;

	public static MessageFactory messageFactory = new MessageFactory();

	// Preconditions:
	public ObjectTrainingModel[] objectTrainingModel = new ObjectTrainingModel[23];

	public PLAY_MODE startPlayMode = PLAY_MODE.PM_PlayOn;

	// GameConditions:
	public GameCondition[] gameCondition = new GameCondition[3];

	// training-state flags
	private int prepared = 0;

	private boolean started = false;

	// private boolean finished = false;

	private boolean statsInit = false;

	private LinkedList<CoachAction> list = null;

	private LinkedList<String> info = null;

	private TrainingStatistics stats = null;

	private TrainingStatsDialog tStats = null;

	public void setTrainingStatsDialog(TrainingStatsDialog tsd) {
		this.tStats = tsd;
	}

	public TrainingModel(ClientScene scene) {
		super();
		for (int i = 0; i < 22; i++)
			this.objectTrainingModel[i] = new PlayerTrainingModel(i);
		this.objectTrainingModel[22] = new ObjectTrainingModel(22);
		for (int i = 0; i < this.objectTrainingModel.length; i++) {
			if (i < 22) {
				this.objectTrainingModel[i].setPosition(scene.player[i].pos);
				PlayerTrainingModel ptm = (PlayerTrainingModel) this.objectTrainingModel[i];
				float x = scene.player[i].vel.x;
				float y = scene.player[i].vel.y;
				Vektor v = new Vektor(Vektor.XY, y, x);
				float speed_angle = (float) v.getAngle();
				int speed = (int) ((v.getLength() / 1.2f) * 100);
				ptm.setSpeed_angle(speed_angle);
				ptm.setMax_speed(speed);
				ptm.setMin_speed(speed);
				ptm.setBody_angle(scene.player[i].angle);
			} else {
				this.objectTrainingModel[i].setPosition(scene.ball.pos);
				float x = scene.ball.vel.x;
				float y = scene.ball.vel.y;
				Vektor v = new Vektor(Vektor.XY, y, x);
				float speed_angle = (float) v.getAngle();
				int speed = (int) ((v.getLength() / 2.7f) * 100);
				this.objectTrainingModel[i].setSpeed_angle(speed_angle);
				this.objectTrainingModel[i].setMax_speed(speed);
				this.objectTrainingModel[i].setMin_speed(speed);
			}
		}

		this.startPlayMode = scene.getPlayModeEnum();

		// insert training game conditions:
		this.gameCondition[0] = new PlayModeCondition();
		this.gameCondition[1] = new CycleCondition();
		this.gameCondition[2] = new BallPossessionCondition();

	}

	/**
	 * This method saves the given training-model
	 * 
	 * @return a success-value
	 */
	public static boolean saveTrainingModel(TrainingModel tm, String filename) {
		try {
			System.out.println("saveTrainingModel");
			FileWriter fw = new FileWriter(filename);
			XStream xstream = new XStream();
			String xml = xstream.toXML(tm);
			fw.write(xml);
			fw.flush();
			fw.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * This method loads a trainingmodel from the an xml-file
	 * 
	 * @return - the serialized training-model
	 */
	public static TrainingModel loadTrainingModel(String filename) {
		try {
			BufferedReader buff = new BufferedReader(new FileReader(filename));
			StringBuffer str = new StringBuffer();
			String tmp = null;
			while ((tmp = buff.readLine()) != null)
				str.append(tmp);

			XStream xstream = new XStream();
			TrainingModel tm = (TrainingModel) xstream.fromXML(str.toString());

			if (!tm.statsInit)
				tm.initStats();
			return tm;

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * This method checks, if the training-scenario is prepared, meaning, that
	 * all objects are at their destinated positions and have the specified
	 * properties
	 * 
	 * @return true, if preparation is done.
	 */
	public boolean isPrepared() {
		return (this.prepared == 3);
	}

	/**
	 * This method prepares the training-scenario, by executing the following
	 * actions: 1. Place all objects, which are part of the scenario to their
	 * destinated position 2. Place all objects, which are not part of the
	 * scenario very far outside the field, for example, all players who are not
	 * active 3. For all players with round-view (and activity), let them turn,
	 * so that they can see everything in their near environment 4. recovery, if
	 * necessary
	 * 
	 * @return - a linked-list with trainer-commands
	 */
	public void prepareTrainingScenario() {
		if (this.list == null)
			this.list = new LinkedList<CoachAction>();
		if (this.info == null)
			this.info = new LinkedList<String>();
		switch (this.prepared) {
		case 0:
			// this sets all positions of objects, which are
			// active, all others (only players) are killed on
			// server-side with command (kill_player [r|l] <num>)
			ClientScene cs = WorldModel.getInstance().getSceneSet().lastScene();
			for (int i = 0; i < this.objectTrainingModel.length; i++) {
				if (i < 11) {
					CoachAction x = this.objectTrainingModel[i]
							.getTrainerAction(i, cs.left);
					if (x != null)
						this.list.add(x);
				} else if (i < 22) {
					CoachAction x = this.objectTrainingModel[i]
							.getTrainerAction(i, cs.right);
					if (x != null)
						this.list.add(x);
				} else {
					CoachAction x = this.objectTrainingModel[i]
							.getTrainerAction(i, null);
					if (x != null)
						this.list.add(x);
				}
			}

			LinkedList<String> dead = new LinkedList<String>();
			for (int i = 0; i < this.list.size(); i++) {
				CoachAction ca = this.list.get(i);

				WorldModel.getInstance().trainer.sendActions(ca.toString());
				if (ca.getType() == Action.TYPE.COACH_KILL_PLAYER) {
					dead.add("" + (i + 1));
				}
				try {
					Thread.sleep(100);
				} catch (Exception e) {

				}
			}

			// broadcast killed players:
			for (int i = 0; i < dead.size(); i++) {
				String str = messageFactory.encodeKillPlayer(Integer
						.parseInt(dead.get(i)));
				SayAction say = new SayAction(str, false);
				WorldModel.getInstance().trainer.sendActions(say.toString());
			}
			this.prepared++;
			break;
		case 1:
			String playmode = PlayMode
					.getPlayModeString(PlayMode.allPlayModes[PlayMode.PM_BeforeKickOff]);
			CoachAction cax = new CoachAction(playmode);
			WorldModel.getInstance().trainer.sendActions(cax.toString());
			// after placing all players, the positions of all players
			// will ce communicated by the trainer. This will replace the round
			// view
			// function. However, it will also run much faster!
			Player player = Player.getDummyInstance();
			for (int i = 0; i < this.list.size(); i++) {
				if (this.objectTrainingModel[i] instanceof PlayerTrainingModel) {
					PlayerTrainingModel ptm = (PlayerTrainingModel) this.objectTrainingModel[i];
					if (ptm.isRoundView() && ptm.isActive()) {
						CoachAction ca = this.list.get(i);
						Vektor pos = ca.getPosition();
						Vektor spd = ca.getSpeed();
						int unum = ca.getNumber();
						boolean isFriend = (ca.getTeam().indexOf("DAI") >= 0);
						// if (unum > 11) {
						// unum -= 11;
						// isFriend = false;
						// }
						player.setBodyCycle(0);
						player.setCycle(0);
						player.setFriend(isFriend);
						player.setNumber(unum);
						player.setPosition(pos);
						player.setSpeed(spd);

						this.info.add(messageFactory
								.encodeTrainerPlayerPosition(player));
						this.info.add(messageFactory
								.encodeTrainerPlayerSpeed(player));
					}
				} else { // Ball
					CoachAction ca = this.list.get(i);
					Vektor pos = ca.getPosition();
					Vektor spd = ca.getSpeed();
					Ball ball = new Ball(0, 0, pos, spd);
					this.info.add(messageFactory
							.encodeTrainerBallPosition(ball));
					this.info.add(messageFactory.encodeTrainerBallSpeed(ball));
				}
			}
			this.prepared++;
			break;
		case 2:
			// here, the list of actions from round-view creation are
			// removed and executed, one in each call of this method!
			if (this.info.isEmpty()) {
				this.prepared++;
			} else {
				String mess = this.info.removeFirst();
				SayAction say = new SayAction(mess, false);
				WorldModel.getInstance().trainer.sendActions(say.toString());
			}
			break;
		}
	}

	/**
	 * This method simply changes the Play-Mode to the desired one. In most
	 * cases, this will be PLAY_ON, but for Standards, this may also be:
	 * KICK_OFF, KICK_IN, OFFSIDE, etc.
	 */
	public void startTrainingScenario() {
		if (WorldModel.sceneSet.lastScene().time > 6000
				&& WorldModel.sceneSet.lastScene().time != Time.MAX_TIME)
			return;
		String playmode = PlayMode.getPlayModeString(this.startPlayMode);
		CoachAction ca = new CoachAction(playmode);
		WorldModel.getInstance().trainer.sendActions(ca.toString());
		this.started = true;
		System.out.println("Szenario Run Nr.: " + run + " PlayMode: "
				+ ca.toString());
		run++;
	}

	/**
	 * This method checks, if the scenario is already started!
	 * 
	 * @return true, if the scenario is running:
	 */
	public boolean isStarted() {
		return this.started;
	}

	/**
	 * This method resets the scenario, so that the trainer will set it up for a
	 * new iteration. To do this, prepared and started are set to false
	 * (prepared = 0).
	 */
	public void resetScenario() {
		this.prepared = 0;
		this.started = false;
		this.list = new LinkedList<CoachAction>();
		this.info = new LinkedList<String>();

		// reset PlayMode to 'BEFORE_KICK_OFF'
		String playmode = PlayMode
				.getPlayModeString(PlayMode.allPlayModes[PlayMode.PM_BeforeKickOff]);
		CoachAction ca = new CoachAction(playmode);
		WorldModel.getInstance().trainer.sendActions(ca.toString());

		// set recover stamina/effort to agents
		ca = new CoachAction(Action.TYPE.COACH_RECOVER);
		WorldModel.getInstance().trainer.sendActions(ca.toString());

		for (int i = 0; i < this.gameCondition.length; i++) {
			this.gameCondition[i].resetCondition();
		}
	}

	/**
	 * This method checks, if some of the abort-conditions are satisfield. If
	 * so, the trainings-sceanrio have to be restarted, and therefore a true has
	 * to be returned!
	 * 
	 * @return true, if the iteration is over.
	 */
	public boolean checkScenarioConditions(ClientScene scene) {
		for (int i = 0; i < this.gameCondition.length; i++) {
			if (this.gameCondition[i].isActive())
				if (this.gameCondition[i].isTrue(scene)) {
					// increase counter for training runs
					this.stats.incElement(0);
					// increase counter for condition
					if (i == 1)
						this.stats.incElement(1);
					if (i == 2)
						this.stats.incElement(2);
					if (i == 0) {
						this.stats
								.incElement(((PlayModeCondition) (this.gameCondition[0]))
										.getFulfillingIndex());
					}
					if (tStats != null)
						this.tStats.update();
					return true;
				}
		}
		return false;
	}

	private void initStats() {
		this.stats = new TrainingStatistics();
		if (this.gameCondition[1].isActive()) {
			this.stats.addElement(((CycleCondition) this.gameCondition[1])
					.getStatIndex());
		}
		if (this.gameCondition[2].isActive()) {
			this.stats
					.addElement(((BallPossessionCondition) this.gameCondition[2])
							.getStatIndex());
		}
		if (this.gameCondition[0].isActive()) {
			int[] pmodes = ((PlayModeCondition) this.gameCondition[0])
					.getStatIndizes();
			for (int i = 0; i < pmodes.length; i++) {
				this.stats.addElement(pmodes[i]);
			}
		}
		this.statsInit = true;
	}

	public TrainingStatsElement[] getStats() {
		return this.stats.getElementsArray();
	}
}
